package com.bjnet.castdemo.bjcast.imp;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.Surface;

import com.bjnet.castdemo.CastManager;
import com.bjnet.castdemo.DemoApplication;
import com.bjnet.castdemo.base.GlSession;
import com.bjnet.castdemo.base.MediaConfHelper;
import com.bjnet.castdemo.event.FullScreenEvent;
import com.bjnet.castdemo.event.MouseEvent;
import com.bjnet.castdemo.event.VideoRotateEvent;
import com.bjnet.castdemo.event.VideoSizeEvent;
import com.bjnet.cbox.module.AudioTrackInfo;
import com.bjnet.cbox.module.BJCastModule;
import com.bjnet.cbox.module.ComBuffer;
import com.bjnet.cbox.module.ComBufferPool;
import com.bjnet.cbox.module.MediaChannel;
import com.bjnet.cbox.module.MediaChannelInfo;
import com.bjnet.cbox.module.MediaConst;
import com.bjnet.cbox.module.Util;

import org.greenrobot.eventbus.EventBus;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by Administrator on 2017/9/12.
 */

public class GLScreenRenderChannel extends MediaChannel {
    private static final String TAG = "BJCast";

    private MediaCodec audioDecoder = null;
    private MediaCodec.BufferInfo audioInfo = null;
    private HandlerThread audioThread = null;
    private Handler audioHandler = null;
    private HandlerThread audioDecodeThread = null;
    private Handler audioDecodeHandler = null;
    private Runnable audioDecodeRunner = null;
    private Runnable audioRunner = null;
    private ReentrantLock audioLock = new ReentrantLock();
    private Condition audioCond = audioLock.newCondition();
    private AudioTrack audioPlayer = null;
    private AudioPlayerStatInfo audioStat = new AudioPlayerStatInfo();

    private MediaCodec videoDecoder = null;
    private Handler videoDecodeHandler = null;
    private Runnable videoDecoderWorker = null;
    private MediaCodec.BufferInfo info = null;
    private HandlerThread videoDecodeThread = null;
    private ConcurrentLinkedQueue<ComBuffer> bufferedVideoQueue = new ConcurrentLinkedQueue<ComBuffer>();
    private ConcurrentLinkedQueue<ComBuffer> bufferedAudioQueue = new ConcurrentLinkedQueue<ComBuffer>();
    private ConcurrentLinkedQueue<ComBuffer> bufferedPcmAudioQueue = new ConcurrentLinkedQueue<ComBuffer>();

    private VideoStatInfo videoStat = new VideoStatInfo();
    private MediaFormat format = null;
    //private ComBufferPool bufferPool = new ComBufferPool();
    private Random random = new Random(System.currentTimeMillis());

    private static final int MAX_BUFFERED_AUDIO_SIZE_FOR_MIRROR = 48000 * 2; //500ms
    private static final int MAX_BUFFERED_AUDIO_SIZE_FOR_MIRROR_AFTERDROP = 48000; //250ms
    private static final int MAX_GAP_STATE_ROUND_NUM = 5;
    private static final int MAX_GAP_STATE_ROUND_SIZE_LIMIT = 9600; //50ms
    private static final int MAX_AVSYNC_LIMIT_SIZE = 38400; //200ms
    private int[] gapSizeStateArray = new int[MAX_GAP_STATE_ROUND_NUM];
    private int gapCount = 0;
    private boolean canDropSomeAudio = false;
    private Bitmap bmp;

    private int videoWidth = -1;
    private int videoHeight = -1;
    private boolean firstDecodeOut = true;
    private  Timer timer = null;
    private int videoRotate = 0;

    public GLScreenRenderChannel(MediaChannelInfo info) {
        super(info);
        glSession = null;
    }

    public GlSession getGlSession() {
        return glSession;
    }

    public void setGlSession(GlSession glSession) {
        this.glSession = glSession;
        BJCastModule castModule = CastManager.getMgr().getCastModule();
        castModule.setRenderBuffer(this, glSession.getBuffer(), glSession.getBufferSize());
    }

    private GlSession glSession;

//    @Override
//    public ComBuffer reqBuffer(int size) {
//        return null;
//    }
//
//    @Override
//    public void renderMediaData(ComBuffer data, int trackId) {
//
//    }

    public boolean openMediaCodecDecoder() {
        if (Build.VERSION.SDK_INT < 19) {
            return true;
        }

        try {
            videoDecoder = MediaCodec.createDecoderByType("video/avc");
            //videoDecoder.setCallback();
        } catch (IOException e) {
            Log.e(TAG, "createDecoder failed" + e.getMessage());
            return false;
        }
        if (videoDecoder == null) {
            Log.e(TAG, "Can't find video info!");
            return false;
        }
        format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080);
        format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 446859);
        format.setInteger(MediaFormat.KEY_FRAME_RATE, 60);
        videoDecoder.configure(format, this.surface, null, 0);
        Log.d(DemoApplication.TAG, "videoDecoder.configure");
        if (null != surface) {
            videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
            Log.d(DemoApplication.TAG, "videoDecoder.setVideoScalingMode");
        }
        videoDecoder.start();
        this.info = new MediaCodec.BufferInfo();

        this.videoDecodeThread = new HandlerThread("ScreenRender_" + getChannelId());
        this.videoDecodeThread.start();
        this.videoDecodeHandler = new ScreenRenderChannelHandler(this.videoDecodeThread.getLooper());
        this.videoDecoderWorker = new VideoDecoderWorker();
        this.videoDecodeHandler.post(videoDecoderWorker);
        return true;
    }

    void closeMediaCodecDecoder() {
        if (Util.getApiLevel() < 19) {
            return;
        }
        if (this.videoDecodeThread != null) {
            this.videoDecodeHandler.post(videoDecoderWorker);
            this.videoDecodeThread.quit();
            try {
                this.videoDecodeThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.videoDecodeThread = null;
            this.videoDecodeHandler = null;
        }
        Log.i(DemoApplication.TAG, "channel videoDecodeThread: stop now");
        if (videoDecoder != null) {
            videoDecoder.flush();
            videoDecoder.stop();
            videoDecoder.release();
            videoDecoder = null;
        }
    }

    @Override
    public boolean open() {
        if (!creeateAudioPlayer()) {
            return false;
        }

        if (MediaConfHelper.getInstance().isEnableChannelStat()){
            this.timer = new Timer();
            this.timer.schedule(new GLScreenRenderChannel.ChannelStatTask(), 1000, 1000);
        }

        return openMediaCodecDecoder();
    }

    private boolean creeateAudioPlayer() {
        if (this.getMediaInfo().getAudioTrack().getAudioCodecType() == MediaConst.AUDIO_TRACK_AAC) {
            return createAacPlayer();
        } else if (this.getMediaInfo().getAudioTrack().getAudioCodecType() == MediaConst.AUDIO_TRACK_PCM) {
            return createPcmPlayer();
        }
        return true;
    }

    private boolean createAacPlayer() {

        try {

            this.audioDecoder = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);

        } catch (IOException e) {

            Log.e(DemoApplication.TAG, "createaudioDecoder failed" + e.getMessage());

            return false;

        }

        if (audioDecoder == null) {
            Log.e(DemoApplication.TAG, "Can't find audio info!");
            return false;
        }

        MediaFormat audioFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 2);

        this.audioDecoder.configure(audioFormat, null, null, 0);
        this.audioDecoder.start();
        ByteBuffer[] inputBuffers = audioDecoder.getInputBuffers();


        int minSize = AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);
        Log.e(DemoApplication.TAG, "audioDecoder! getInputBuffers size:" + inputBuffers.length + " minSize:" + minSize);
        audioPlayer = new AudioTrack(AudioManager.STREAM_MUSIC, 48000,
                AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, minSize, AudioTrack.MODE_STREAM);
        audioPlayer.play();
        this.audioInfo = new MediaCodec.BufferInfo();

        this.audioThread = new HandlerThread("channel_pcm_audio_" + this.getChannelId());
        this.audioThread.start();
        this.audioRunner = new PcmRender();
        this.audioHandler = new Handler(this.audioThread.getLooper());
        this.audioHandler.post(this.audioRunner);

        return true;

    }

    public void setAudioTrackCodecInfo(byte[] data, String metaDscp) {

        Log.i(DemoApplication.TAG, "setTrackCodecInfo metaDscp:" + metaDscp);


        MediaFormat audioFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 2);

//            static const UCHAR AAC_CONFIG[] = { 0xb9, 0x91, 0 };

        ByteBuffer aacConf = ByteBuffer.allocate(5);

        aacConf.put(data);

        aacConf.put((byte) 0x00);

        aacConf.flip();

        Log.i(DemoApplication.TAG, "conf aac codec:" + aacConf + " metaDscp:" + metaDscp);

        audioFormat.setByteBuffer(metaDscp, aacConf);

        if (this.audioDecoder != null) {

            this.audioDecoder.stop();

            this.audioDecoder.configure(audioFormat, null, null, 0);

            this.audioDecoder.start();

        }

        this.audioDecodeThread = new HandlerThread("channel_audio_decode_" + this.getChannelId());
        this.audioDecodeThread.start();

        this.audioDecodeRunner = new AudioDecoderAndRender();

        this.audioDecodeHandler = new Handler(this.audioDecodeThread.getLooper());
        this.audioDecodeHandler.post(this.audioDecodeRunner);


        Log.i(DemoApplication.TAG, "conf aac codec success:" + aacConf + " metaDscp:" + metaDscp);


        return;

    }


    private boolean createPcmPlayer() {
        AudioTrackInfo ainfo = this.getMediaInfo().getAudioTrack();
        int minSize = AudioTrack.getMinBufferSize(ainfo.getSampleRate(), AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);
        audioPlayer = new AudioTrack(AudioManager.STREAM_MUSIC, ainfo.getSampleRate(),
                AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, minSize, AudioTrack.MODE_STREAM);
        audioPlayer.play();

        this.audioThread = new HandlerThread("channel_pcm_audio_" + this.getChannelId());
        this.audioThread.start();
        this.audioRunner = new PcmRender();
        this.audioHandler = new Handler(this.audioThread.getLooper());
        this.audioHandler.post(this.audioRunner);

        return true;
    }

    @Override
    public void close() {
        //send close
        Log.i(TAG, "GLScreenRenderChannel close: start");
        setState(MCState.MC_DEAD);
//        Handler handler = CastManager.getMgr().getUiHandler();
//        if (handler != null){
//            Message msg = new Message();
//            msg.what = CastManager.MSG_CHANNEL_CLOSED;
//
//            handler.sendMessage(msg);
//        }

        if(timer != null)
        {
            timer.cancel();
            timer = null;
        }

        closeMediaCodecDecoder();

        if (this.audioDecodeThread != null) {
            this.audioDecodeHandler.post(this.audioDecodeRunner);
            this.audioDecodeThread.quit();
            try {
                this.audioDecodeThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (audioDecoder != null) {
                audioDecoder.stop();
                audioDecoder.release();
                audioDecoder = null;
            }
        }

        if (this.audioHandler != null) {
            try {
                audioLock.lock();
                audioCond.signal();
            } finally {
                audioLock.unlock();
            }
            this.audioHandler.post(this.audioRunner);
            this.audioThread.quit();
            try {
                this.audioThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (audioPlayer != null) {
                audioPlayer.stop();
                audioPlayer.release();
                audioPlayer = null;
            }
        }

        Log.i(TAG, "GLScreenRenderChannel close: end");
        this.glSession = null;
    }

    @Override
    public void onFrame(int frameLen, int w, int h) {
        if (glSession != null) {
            glSession.onFrame(frameLen, w, h);
        }
    }

    @Override
    public void onAudioFrame(byte[] buffer,int len,long ts) {
        ComBuffer data = new ComBuffer(buffer,len,ts);
        if (this.getMediaInfo().getAudioTrack().getAudioCodecType() == MediaConst.AUDIO_TRACK_PCM) {
////                assert this.audioPlayer != null;
//                this.audioPlayer.write(data.buffer, 0, data.getLen());
//                bufferPool.relBuffer(data);
            this.bufferedPcmAudioQueue.offer(data);
            //latestAudioTs = data.getTs();
            ++audioStat.audioFramesInputThisRound;
            audioStat.audioSizeInThisRound += data.getLen();
            audioStat.audioSizeInBuffer.addAndGet(data.getLen());
            ++audioStat.totalAudioFramesInput;
            //dropTooOldAudio();
            try {
                audioLock.lock();
                audioCond.signal();
            } finally {
                audioLock.unlock();
            }

//                bufferPool.relBuffer(data);
        } else {
            this.bufferedAudioQueue.offer(data);
        }
        assert false;
    }

//    @Override
//    public void onVideoFrame(ComBuffer buffer) {
//        bufferedVideoQueue.offer(buffer);
//        ++videoStat.videoFramesInputThisRound;
//        ++videoStat.totalVideoFramesInput;
//    }

//    @Override
//    public ComBuffer reqBuffer(int size) {
//        return this.bufferPool.reqBuffer(size);
//    }

    private int getWidthFromMediaFormat(MediaFormat format) {
        int width = 0;
        if (format.containsKey("crop-right") && format.containsKey("crop-left")) {
            int crop_right = format.getInteger("crop-right");
            int crop_left = format.getInteger("crop-left");
            width = crop_right + 1 - crop_left;
        } else if (format.containsKey(MediaFormat.KEY_WIDTH)) {
            width = format.getInteger(MediaFormat.KEY_WIDTH);
            Log.d(TAG, "onOutputFormatChanged format width = " + width + " format:" + format.toString());

        }
        return width;
    }

    private int getHeightFromMediaFormat(MediaFormat format) {
        int height = 0;
        if (format.containsKey("crop-top") && format.containsKey("crop-bottom")) {
            height = format.getInteger("crop-bottom") + 1 - format.getInteger("crop-top");
        } else if (format.containsKey(MediaFormat.KEY_HEIGHT)) {
            height = format.getInteger(MediaFormat.KEY_HEIGHT);
            Log.d(TAG, "onOutputFormatChanged format width = " + height + " format:" + format.toString());
        }
        return height;
    }

    private void decodeVideoAndRender(long timeoutUs) {
        ByteBuffer[] outputBuffers = videoDecoder.getOutputBuffers();
        int outIndex = videoDecoder.dequeueOutputBuffer(info, timeoutUs);
        switch (outIndex) {
            case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:

                Log.d(TAG, "INFO_OUTPUT_BUFFERS_CHANGED");
                outputBuffers = videoDecoder.getOutputBuffers();

                break;

            case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:

                Log.d(TAG, "New format " + videoDecoder.getOutputFormat());
            {
//                Message msg = new Message();
//
//                msg.what = CastManager.MSG_CHANNEL_CONTENT_SIZE_CHANGED;

                MediaFormat fmt = videoDecoder.getOutputFormat();

                VideoSizeEvent event = new VideoSizeEvent(channelId, getWidthFromMediaFormat(fmt), getHeightFromMediaFormat(fmt));
                DemoApplication.APP.getEventBus().post(event);
            }

            break;

            case MediaCodec.INFO_TRY_AGAIN_LATER:

                // Log.d("CM", "dequeueOutputBuffer timed out!");

                break;

            default:

                ByteBuffer buffer = outputBuffers[outIndex];

                if(firstDecodeOut){
                    firstDecodeOut = false;
                    Log.i(DemoApplication.TAG, "decodeVideoAndRender first out frame input num:"+videoStat.totalVideoFramesInput);
                }

                int bufferVedioSize = bufferedVideoQueue.size();
                if (bufferVedioSize > 30) {
                    if ((random.nextInt()) % 4 == 1) {
                        ++videoStat.videoFramesRenderThisRound;
                        videoDecoder.releaseOutputBuffer(outIndex, MCState.MC_RUN_FRONT == state);
                    } else {
                        ++videoStat.videoFramesRenderFailedThisRound;
                        videoDecoder.releaseOutputBuffer(outIndex, false);
                    }
                } else if (bufferVedioSize >= 25) {
                    if ((random.nextInt()) % 2 == 1) {
                        ++videoStat.videoFramesRenderFailedThisRound;
                        videoDecoder.releaseOutputBuffer(outIndex, false);
                    } else {
                        ++videoStat.videoFramesRenderThisRound;
                        videoDecoder.releaseOutputBuffer(outIndex, MCState.MC_RUN_FRONT == state);
                    }
                } else if (bufferVedioSize >= 12) {
                    if ((random.nextInt()) % 3 == 1) {
                        ++videoStat.videoFramesRenderFailedThisRound;
                        videoDecoder.releaseOutputBuffer(outIndex, false);
                    } else {
                        ++videoStat.videoFramesRenderThisRound;
                        videoDecoder.releaseOutputBuffer(outIndex, MCState.MC_RUN_FRONT == state);
                    }
                } else if (bufferVedioSize >= 6) {
                    if ((random.nextInt()) % 4 == 1) {
                        ++videoStat.videoFramesRenderFailedThisRound;
                        videoDecoder.releaseOutputBuffer(outIndex, false);
                    } else {
                        ++videoStat.videoFramesRenderThisRound;
                        videoDecoder.releaseOutputBuffer(outIndex, MCState.MC_RUN_FRONT == state);
                    }
                } else {
                    ++videoStat.videoFramesRenderThisRound;
                    videoDecoder.releaseOutputBuffer(outIndex, MCState.MC_RUN_FRONT == state);
                }

                videoStat.videoFramesOutThisRound++;
                ++videoStat.totalVideoFramesOut;
                break;
        }
    }

    public void setSurface(Surface surface) {
        Log.i(TAG, "setSurface surface:" + surface);
        this.surface = surface;

        if (MCState.MC_DEAD == this.state) {
            return;
        }

        Message msg = new Message();
        msg.what = (this.surface == null) ? CastManager.MSG_UI_SURFACE_DESTROYED : CastManager.MSG_UI_SURFACE_CREATED;
        if (this.videoDecodeHandler != null) {
            this.videoDecodeHandler.sendMessage(msg);
        }
    }

    public int getVideoRotate() {
        return videoRotate;
    }

    private class ChannelStatTask extends TimerTask
    {
        int count = 0;
        @Override
        public void run()
        {
            videoStat.videoFramesInBuffer = bufferedVideoQueue.size();

            Log.i("CHANNEL_STAT", "channel id:" + getChannelId() + " " + videoStat.toString()+ " " + audioStat.toString() + " poolStat:");

            videoStat.reset();
            audioStat.reset();

            //bufferPool.checkAndFreeIdleBuffer();
        }
    }

    private class VideoStatInfo {
        public int videoFramesInputThisRound;
        public int totalVideoFramesInput;
        public int videoFramesOutThisRound;
        public int totalVideoFramesOut;
        public int videoFramesInBuffer;
        public int videoFramesQueneToDecodeThisRound;
        public int videoFramesQueneToDecodeThisRoundFailed;
        public int videoFramesDecodeOutThisRound;
        public int videoLoopCountThisRound;
        public int videoFramesRenderThisRound;
        public int videoFramesRenderFailedThisRound;

        public VideoStatInfo() {
            videoFramesInputThisRound = 0;
            totalVideoFramesInput = 0;
            videoFramesOutThisRound = 0;
            totalVideoFramesOut = 0;
            videoFramesInBuffer = 0;
            videoFramesQueneToDecodeThisRound = 0;
            videoFramesDecodeOutThisRound = 0;
            videoLoopCountThisRound = 0;
            videoFramesRenderThisRound = 0;
            videoFramesRenderFailedThisRound = 0;
        }

        void reset() {
            videoFramesInputThisRound = 0;
            videoFramesOutThisRound = 0;
            videoFramesQueneToDecodeThisRoundFailed = 0;
            videoFramesQueneToDecodeThisRound = 0;
            videoLoopCountThisRound = 0;
            videoFramesRenderThisRound = 0;
            videoFramesRenderFailedThisRound = 0;
        }

        @Override
        public String toString() {
            return "VideoStatInfo{" +
                    "videoFramesInputThisRound=" + videoFramesInputThisRound +
                    ", totalVideoFramesInput=" + totalVideoFramesInput +
                    ", videoFramesOutThisRound=" + videoFramesOutThisRound +
                    ", totalVideoFramesOut=" + totalVideoFramesOut +
                    ", videoFramesInBuffer=" + videoFramesInBuffer +
                    ", videoFramesQueneToDecodeThisRound=" + videoFramesQueneToDecodeThisRound +
                    ", videoFramesQueneToDecodeThisRoundFailed=" + videoFramesQueneToDecodeThisRoundFailed +
                    ", videoFramesDecodeOutThisRound=" + videoFramesDecodeOutThisRound +
                    ", videoLoopCountThisRound=" + videoLoopCountThisRound +
                    ", videoFramesRenderThisRound=" + videoFramesRenderThisRound +
                    ", videoFramesRenderFailedThisRound=" + videoFramesRenderFailedThisRound +
                    '}';
        }
    }

    private class VideoDecoderWorker implements Runnable {
        private boolean isWaitIFrame = true;
        private long firstTs = 0;
        private boolean firstInFrame = true;
        @Override
        public void run() {
            videoStat.videoLoopCountThisRound++;
            if (MCState.MC_DEAD == state) {
                Log.d(TAG, "GLScreenRenderChannel.run() state = MC_DEAD id:" + getChannelId());
                return;
            }

            if (state != MCState.MC_RUN_FRONT) {
                if (bufferedVideoQueue.size() > 120) {
                    Log.i(TAG, "run: buffered too many video exit this session");
                    MediaChannel channel = CastManager.getMgr().getChannelById(channelId);
                    if (channel != null) {
                        CastManager.getMgr().getCastModule().kickOut(channel);
                    }

                    return;
                }
                if (MCState.MC_DEAD != state)
                    videoDecodeHandler.postDelayed(this, 10);
                return;
            }

            while (!bufferedVideoQueue.isEmpty() && (state != MCState.MC_DEAD)) {
                if (isWaitIFrame) {
                    ComBuffer b = bufferedVideoQueue.peek();
                    if (!Util.isAvcKeyFrame(b)) {
                        //bufferPool.relBuffer(bufferedVideoQueue.poll());
                        Log.i(DemoApplication.TAG, "run: drop a video when wait IFrame");
                        continue;
                    } else {
                        isWaitIFrame = false;
                        Log.i(DemoApplication.TAG, "run: get IFrame now");
                    }
                }

                ByteBuffer[] inputBuffers = videoDecoder.getInputBuffers();
                int inIndex = videoDecoder.dequeueInputBuffer(0);
                if (inIndex >= 0) {

                    ByteBuffer buffer = inputBuffers[inIndex];

                    buffer.clear();
                    long ts = 0;

                    ComBuffer b = bufferedVideoQueue.poll();

                    buffer.put(b.buffer, 0, b.getLen());
                    ts = b.getTs();
                    //bufferPool.relBuffer(b);
                    buffer.flip();
                    ++videoStat.videoFramesQueneToDecodeThisRound;
                    if(firstInFrame){
                        firstInFrame = false;
                        videoDecoder.queueInputBuffer(inIndex, 0, buffer.limit(), ts, 0);
                        firstTs = ts;
                    }else{
                        videoDecoder.queueInputBuffer(inIndex, 0, buffer.limit(), (ts-firstTs), 0);
                    }

                    decodeVideoAndRender(0);
                } else

                {
                    ++videoStat.videoFramesQueneToDecodeThisRoundFailed;
                    decodeVideoAndRender(0);
                    videoDecodeHandler.postDelayed(this, 10);
                    //handler.post(this);
                    return;
                }
            }


            decodeVideoAndRender(0);
            videoDecodeHandler.postDelayed(this, 10);
        }
    }

    private class ScreenRenderChannelHandler extends Handler {

        public ScreenRenderChannelHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            if (msg.what == CastManager.MSG_UI_SURFACE_CREATED) {
                if (videoDecoder != null) {
                    Log.i(DemoApplication.TAG, "handleMessage MSG_UI_SURFACE_CREATED: start");
                    videoDecoder.flush();
                    videoDecoder.stop();
                    videoDecoder.release();
                    videoDecoder = null;

                    try {
                        videoDecoder = MediaCodec.createDecoderByType("video/avc");
                        //videoDecoder.setCallback();
                    } catch (IOException e) {
                        Log.e(TAG, "createDecoder failed" + e.getMessage());
                        return;
                    }
                    videoDecoder.configure(format, surface, null, 0);
                    videoDecoder.start();
                    Log.i(DemoApplication.TAG, "handleMessage MSG_UI_SURFACE_CREATED: over");
                }

                setState(MCState.MC_RUN_FRONT);
            } else if (msg.what == CastManager.MSG_UI_SURFACE_DESTROYED) {

                if (videoDecoder != null) {
                    videoDecoder.flush();
                    videoDecoder.stop();
                    videoDecoder.release();
                    videoDecoder = null;

                    try {
                        videoDecoder = MediaCodec.createDecoderByType("video/avc");
                        //videoDecoder.setCallback();
                    } catch (IOException e) {
                        Log.e(TAG, "createDecoder failed" + e.getMessage());
                        return;
                    }
                    videoDecoder.configure(format, surface, null, 0);
                    videoDecoder.start();
                }

                setState(MCState.MC_RUN_BACK);
            }
        }
    }

    private class PcmRender implements Runnable {
        @Override
        public void run() {
            if (MCState.MC_DEAD == state) {
                if (audioPlayer != null) {
                    audioPlayer.stop();
                    audioPlayer.release();
                    audioPlayer = null;
                }
                Log.d(DemoApplication.TAG, "PcmRender exit channel:" + getChannelId());
                return;
            }

            try {
                audioLock.lock();
                audioCond.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                audioLock.unlock();
            }

            while (!bufferedPcmAudioQueue.isEmpty() && (state != MCState.MC_DEAD)) {

                if (audioStat.audioSizeInBuffer.get() >= MAX_BUFFERED_AUDIO_SIZE_FOR_MIRROR && MediaConfHelper.getInstance().isEnableChannelDropAudio()) {
                    dropTooOldAudio();
                    canDropSomeAudio = false;
                    for (int i = 0; i < MAX_GAP_STATE_ROUND_NUM; ++i) {
                        gapSizeStateArray[i] = MAX_GAP_STATE_ROUND_SIZE_LIMIT + 1;
                    }
                } else if (audioStat.audioSizeInBuffer.get() > MAX_AVSYNC_LIMIT_SIZE && canDropSomeAudio && MediaConfHelper.getInstance().isEnableChannelDropAudio()) {
                    dropSomeTooOldAudio();
                    canDropSomeAudio = false;
                    for (int i = 0; i < MAX_GAP_STATE_ROUND_NUM; ++i) {
                        gapSizeStateArray[i] = MAX_GAP_STATE_ROUND_SIZE_LIMIT + 1;
                    }
                } else {
                    ComBuffer buffer = bufferedPcmAudioQueue.poll();
                    audioPlayer.write(buffer.getBuffer(), 0, buffer.getLen());
                    ++audioStat.totalAudioFramesOut;
                    ++audioStat.audioFramesOutThisRound;
                    audioStat.audioSizeOutThisRound += buffer.getLen();
                    audioStat.audioSizeInBuffer.addAndGet(-1 * buffer.getLen());
                    //bufferPool.relBuffer(buffer);
                }
            }

            if (state != MCState.MC_DEAD) {
                if (audioHandler != null) {
                    audioHandler.post(this);
                }
            } else {
                if (audioPlayer != null) {
                    audioPlayer.stop();
                    audioPlayer.release();
                    audioPlayer = null;
                }
                Log.d(DemoApplication.TAG, "PcmRender exit channel:" + getChannelId());
            }
        }
    }

    private class AudioPlayerStatInfo {
        public int audioFramesInputThisRound;
        public int totalAudioFramesInput;
        public int totalAudioFramesOut;
        public int audioFramesOutThisRound;
        public int audioFramesDropThisRound;
        public int audioSizeInThisRound;
        public int audioSizeOutThisRound;
        public int audioFramesInBuffer;
        public AtomicInteger audioSizeInBuffer = new AtomicInteger(0);
        public int dropAudioSizeThisRound;
        public int lastRoundAudioFramesInBuffer;

        public AudioPlayerStatInfo() {
            audioFramesInputThisRound = 0;
            totalAudioFramesInput = 0;
            totalAudioFramesOut = 0;
            audioFramesOutThisRound = 0;
            audioSizeInThisRound = 0;
            audioSizeOutThisRound = 0;
            audioFramesInBuffer = 0;
            audioSizeInBuffer.set(0);
            dropAudioSizeThisRound = 0;
            lastRoundAudioFramesInBuffer = 0;
        }

        void reset() {
            audioFramesInputThisRound = 0;
            audioFramesOutThisRound = 0;
            audioSizeInThisRound = 0;
            audioSizeOutThisRound = 0;
            dropAudioSizeThisRound = 0;
            audioFramesDropThisRound = 0;
        }


        @Override
        public String toString() {
            return "AudioPlayerStatInfo{" +
                    "totalAudioFramesInput=" + totalAudioFramesInput +
                    ", totalAudioFramesOut=" + totalAudioFramesOut +
                    ", audioFramesInputThisRound=" + audioFramesInputThisRound +
                    ", audioFramesOutThisRound=" + audioFramesOutThisRound +
                    ", audioFramesDropThisRound=" + audioFramesDropThisRound +
                    ", audioSizeInThisRound=" + audioSizeInThisRound +
                    ", audioSizeOutThisRound=" + audioSizeOutThisRound +
                    ", audioFramesInBuffer=" + audioFramesInBuffer +
                    ", audioSizeInBuffer=" + audioSizeInBuffer.get() +
                    ", dropAudioSizeThisRound=" + dropAudioSizeThisRound +
                    (audioPlayer != null ? ", playerRate=" + audioPlayer.getPlaybackRate() : "") +
                    '}';
        }
    }

    private void dropTooOldAudio() {
        int dropNum = 0;
        while (!bufferedPcmAudioQueue.isEmpty()) {
            if (audioStat.audioSizeInBuffer.get() > MAX_BUFFERED_AUDIO_SIZE_FOR_MIRROR_AFTERDROP) {
                ComBuffer buf = bufferedPcmAudioQueue.poll();
                ++audioStat.totalAudioFramesOut;
                ++audioStat.audioFramesDropThisRound;
                audioStat.dropAudioSizeThisRound += buf.getLen();
                audioStat.audioSizeInBuffer.addAndGet(-1 * buf.getLen());
                //bufferPool.relBuffer(buf);
                ++dropNum;
            } else {
                break;
            }
        }
        Log.w("CHANNEL_STATE", "dropTooOldAudio: dropAudio num:" + dropNum + " channel:" + getChannelId());
    }

    private void dropSomeTooOldAudio() {
        int dropSize = 0;
        int dropNum = 0;
        while (!bufferedPcmAudioQueue.isEmpty()) {
            if (dropSize < MAX_GAP_STATE_ROUND_SIZE_LIMIT) {
                ComBuffer buf = bufferedPcmAudioQueue.poll();
                ++audioStat.totalAudioFramesOut;
                ++audioStat.audioFramesDropThisRound;
                audioStat.dropAudioSizeThisRound += buf.getLen();
                audioStat.audioSizeInBuffer.addAndGet(-1 * buf.getLen());
                dropSize += buf.getLen();
                //bufferPool.relBuffer(buf);
                ++dropNum;
            } else {
                break;
            }
        }
        Log.w("CHANNEL_STATE", "dropSomeTooOldAudio: dropAudio num:" + dropNum + " channel:" + getChannelId());
    }

    private class AudioDecoderAndRender implements Runnable {
        @Override
        public void run() {
//            Log.d(MainActivity.TAG, "AudioDecoderAndRender run()");
            if (MCState.MC_DEAD == state) {
                if (audioDecoder != null) {
                    audioDecoder.stop();
                    audioDecoder.release();
                    audioDecoder = null;
                }
                Log.d(DemoApplication.TAG, "AudioDecoderAndRender exit channel:" + getChannelId());
                return;
            }


            if (!bufferedAudioQueue.isEmpty()) {
                ByteBuffer[] inputBuffers = audioDecoder.getInputBuffers();
                int inIndex = audioDecoder.dequeueInputBuffer(10000);
                if (inIndex >= 0) {
                    ByteBuffer buffer = inputBuffers[inIndex];
                    buffer.clear();
                    ComBuffer b = bufferedAudioQueue.poll();
                    buffer.put(b.buffer, 0, b.getLen());
                    buffer.flip();
                    audioDecoder.queueInputBuffer(inIndex, 0, b.getLen(), b.ts, 0);
                    //bufferPool.relBuffer(b);
//                Log.d(MainActivity.TAG, "renderMediaData success len:" + b.data.length);
                    decodeAudioAndPlay(0);
                    if (bufferedAudioQueue.isEmpty()) {
                        audioDecodeHandler.postDelayed(this, 10);
                    } else {
                        audioDecodeHandler.post(this);
                    }
                } else {
                    decodeAudioAndPlay(10000);
                    //audioHandler.postDelayed(this, 10);
                    audioDecodeHandler.post(this);
                }
            } else {
                decodeAudioAndPlay(0);
                audioDecodeHandler.postDelayed(this, 10);
            }
        }
    }

    private void decodeAudioAndPlay(long timeoutUs) {
        //Log.d(MainActivity.TAG_THREAD, "decodeAudioAndPlay()");
        ByteBuffer[] outputBuffers = audioDecoder.getOutputBuffers();
        int outIndex = audioDecoder.dequeueOutputBuffer(audioInfo, timeoutUs);
        switch (outIndex) {
            case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                Log.d(DemoApplication.TAG, "INFO_OUTPUT_BUFFERS_CHANGED");
                outputBuffers = audioDecoder.getOutputBuffers();
                break;
            case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                Log.d(DemoApplication.TAG, "New format " + audioDecoder.getOutputFormat());
                break;
            case MediaCodec.INFO_TRY_AGAIN_LATER:
                //Log.d(MainActivity.TAG_THREAD, "dequeueOutputBuffer audio timed out!");
                break;
            default:
                if (audioInfo.size > 0) {
                    ByteBuffer buffer = outputBuffers[outIndex];
//                    byte[] audioFrame = new byte[audioInfo.size];
//                    buffer.get(audioFrame);
//                    buffer.clear();
//
//                    audioPlayer.write(audioFrame, 0, audioFrame.length);


                    ComBuffer pcmBuf = new ComBuffer(audioInfo.size);
                    buffer.get(pcmBuf.getBuffer(), 0, audioInfo.size);
                    buffer.clear();
                    pcmBuf.setLen(audioInfo.size);
                    pcmBuf.setTs(0);
                    ++audioStat.audioFramesInputThisRound;
                    audioStat.audioSizeInThisRound += audioInfo.size;
                    audioStat.audioSizeInBuffer.addAndGet(audioInfo.size);
                    ++audioStat.totalAudioFramesInput;
                    this.bufferedPcmAudioQueue.offer(pcmBuf);

                    try {
                        audioLock.lock();
                        audioCond.signal();
                    } finally {
                        audioLock.unlock();
                    }
                    //Log.v(MainActivity.TAG_THREAD, "render audio to player len:" + audioFrame.length);
                }
                audioDecoder.releaseOutputBuffer(outIndex, false);
                break;
        }
    }

    @Override
    public void hindCursor() {
        Log.d(TAG, "hindCursor channel:" + getChannelId());
        EventBus.getDefault().post(new MouseEvent(false,getChannelId(),DemoApplication.MOUSEISSHOW));
    }

    @Override
    public void updateCursorShape(byte[] pngData) {
//        Log.d(TAG, "updateCursorShape channel:" + getChannelId() + " pngData:" + pngData);
        if (pngData.length != 0) {
            bmp = BitmapFactory.decodeByteArray(pngData, 0, pngData.length);  //encode png to bitmap
            EventBus.getDefault().post(new MouseEvent(bmp,getChannelId(),DemoApplication.MOUSEBITMAP));
        }
    }

    @Override
    public void updateCursorPos(short x, short y) {
//        Log.d(TAG, "updateCursorPos channel:" + getChannelId() + " x:" + x + " y:" + y);
        EventBus.getDefault().post(new MouseEvent(bmp, x, y,getChannelId(), DemoApplication.MOUSEISMOVE));
    }

    @Override
    public void ctrl(int type) {
        super.ctrl(type);
        Log.d(TAG, "ctrl: "+type);
        DemoApplication.APP.getEventBus().post(new FullScreenEvent(getChannelId(),type));
    }

    @Override
    public void setVolume(int volume) {
        super.setVolume(volume);
        float v = volume/100.0f;
        if(audioPlayer != null){
            audioPlayer.setStereoVolume(v,v);
        }
    }

    @Override
    public void rotate(int angle){
        if (getVideoRotate() != angle){
            Log.i(TAG, "set rotate: angle:"+angle);
            videoRotate = angle;

            VideoRotateEvent event = new VideoRotateEvent(channelId,angle);
            DemoApplication.APP.getEventBus().post(event);
        }
    }

    @Override
    public  void  onVideoFrame(byte[] buffer,int len,long ts){
        ComBuffer data = new ComBuffer(buffer,len,ts);
        bufferedVideoQueue.offer(data);
        ++videoStat.videoFramesInputThisRound;
        ++videoStat.totalVideoFramesInput;
    }
}
